#include "il2cpp-config.h"

#include <cassert>

#include "icalls/System/System.Net.Sockets/Socket.h"

#include "class-internals.h"
#include "os/Socket.h"
#include "os/Mutex.h"
#include "utils/StringUtils.h"
#include "vm/Array.h"
#include "vm/Assembly.h"
#include "vm/Class.h"
#include "vm/Exception.h"
#include "vm/Field.h"
#include "vm/Object.h"
#include "vm/String.h"
#include "vm/Thread.h"

namespace il2cpp
{
namespace icalls
{
namespace System
{
namespace System
{
namespace Net
{
namespace Sockets
{

static os::AddressFamily convert_address_family (AddressFamily family)
{
	switch(family)
	{
		case kAddressFamilyUnknown:
		case kAddressFamilyImpLink:
		case kAddressFamilyPup:
		case kAddressFamilyChaos:
		case kAddressFamilyIso:
		case kAddressFamilyEcma:
		case kAddressFamilyDataKit:
		case kAddressFamilyCcitt:
		case kAddressFamilyDataLink:
		case kAddressFamilyLat:
		case kAddressFamilyHyperChannel:
		case kAddressFamilyNetBios:
		case kAddressFamilyVoiceView:
		case kAddressFamilyFireFox:
		case kAddressFamilyBanyan:
		case kAddressFamilyAtm:
		case kAddressFamilyCluster:
		case kAddressFamilyIeee12844:
		case kAddressFamilyNetworkDesigners:
			// Unsupported
			return os::kAddressFamilyError;
		
		case kAddressFamilyUnspecified:
			return os::kAddressFamilyUnspecified;
	
		case kAddressFamilyUnix:
			return os::kAddressFamilyUnix;
	
		case kAddressFamilyInterNetwork:
			return os::kAddressFamilyInterNetwork;
	
		case kAddressFamilyIpx:
			return os::kAddressFamilyIpx;
	
		case kAddressFamilySna:
			return os::kAddressFamilySna;
	
		case kAddressFamilyDecNet:
			return os::kAddressFamilyDecNet;
	
		case kAddressFamilyAppleTalk:
			return os::kAddressFamilyAppleTalk;
	
		case kAddressFamilyInterNetworkV6:
			return os::kAddressFamilyInterNetworkV6;
	
		case kAddressFamilyIrda:
			return os::kAddressFamilyIrda;
		
		default:
			return os::kAddressFamilyError;
	}

	return os::kAddressFamilyError;
}

static os::SocketType convert_socket_type (SocketType type)
{
	switch(type)
	{
		case kSocketTypeStream:
			return os::kSocketTypeStream;

		case kSocketTypeDgram:
			return os::kSocketTypeDgram;
	
		case kSocketTypeRaw:
			return os::kSocketTypeRaw;
	
		case kSocketTypeRdm:
			return os::kSocketTypeRdm;
	
		case kSocketTypeSeqpacket:
			return os::kSocketTypeSeqpacket;
		
		default:
			return os::kSocketTypeError;
	}
	
	return os::kSocketTypeError;
}

static os::ProtocolType convert_socket_protocol (ProtocolType protocol)
{
	switch(protocol)
	{
		case kProtocolTypeIP:
		case kProtocolTypeIPv6:
		case kProtocolTypeIcmp:
		case kProtocolTypeIgmp:
		case kProtocolTypeGgp:
		case kProtocolTypeTcp:
		case kProtocolTypePup:
		case kProtocolTypeUdp:
		case kProtocolTypeIdp:
			// In this case the enum values map exactly.
			return (os::ProtocolType) protocol;
			
		case kProtocolTypeND:
		case kProtocolTypeRaw:
		case kProtocolTypeIpx:
		case kProtocolTypeSpx:
		case kProtocolTypeSpxII:
		case kProtocolTypeUnknown:
			// Everything else in unsupported
			return os::kProtocolTypeUnknown;
		
		default:
			return os::kProtocolTypeUnknown;
	}

	return os::kProtocolTypeUnknown;
}

static AddressFamily convert_from_os_address_family(os::AddressFamily family)
{
	switch(family)
	{
		case os::kAddressFamilyUnspecified:
			return kAddressFamilyUnspecified;
			
		case os::kAddressFamilyUnix:
			return kAddressFamilyUnix;
			
		case os::kAddressFamilyInterNetwork:
			return kAddressFamilyInterNetwork;
			
//	#ifdef AF_IPX
		case os::kAddressFamilyIpx:
			return kAddressFamilyIpx;
//	#endif
			
		case os::kAddressFamilySna:
			return kAddressFamilySna;
			
		case os::kAddressFamilyDecNet:
			return kAddressFamilyDecNet;
			
		case os::kAddressFamilyAppleTalk:
			return kAddressFamilyAppleTalk;
			
//	#ifdef AF_INET6
		case os::kAddressFamilyInterNetworkV6:
			return kAddressFamilyInterNetworkV6;
//	#endif
			
//	#ifdef AF_IRDA
		case os::kAddressFamilyIrda:
			return kAddressFamilyIrda;
//	#endif
		default:
			break;
	}

	return kAddressFamilyUnknown;
}

static os::SocketFlags convert_socket_flags (SocketFlags flags)
{
	return (os::SocketFlags) flags;
}

static Il2CppSocketAddress* end_point_info_to_socket_address (const os::EndPointInfo &info)
{
	static Il2CppClass *System_Net_SocketAddress = NULL;
	
	Il2CppSocketAddress *socket_address = NULL;
	
	if (!System_Net_SocketAddress)
	{
		System_Net_SocketAddress = vm::Class::FromName (
			vm::Assembly::GetImage (
				vm::Assembly::Load ("System.dll")),
			"System.Net", "SocketAddress");
	}
	
	socket_address = (Il2CppSocketAddress *)vm::Object::New (System_Net_SocketAddress);
	
	const AddressFamily family = convert_from_os_address_family(info.family);
	
	if (info.family == os::kAddressFamilyInterNetwork)
	{
		socket_address->data = vm::Array::New (il2cpp_defaults.byte_class, 8);
		
		const uint16_t port = info.data.inet.port;
		const uint32_t address = info.data.inet.address;
		
		il2cpp_array_set (socket_address->data, uint8_t, 0, (family >> 0) & 0xFF);
		il2cpp_array_set (socket_address->data, uint8_t, 1, (family >> 8) & 0xFF);
		il2cpp_array_set (socket_address->data, uint8_t, 2, (port >> 8) & 0xFF);
		il2cpp_array_set (socket_address->data, uint8_t, 3, (port >> 0) & 0xFF);
		il2cpp_array_set (socket_address->data, uint8_t, 4, (address >> 24) & 0xFF);
		il2cpp_array_set (socket_address->data, uint8_t, 5, (address >> 16) & 0xFF);
		il2cpp_array_set (socket_address->data, uint8_t, 6, (address >>  8) & 0xFF);
		il2cpp_array_set (socket_address->data, uint8_t, 7, (address >>  0) & 0xFF);
	}
	else if (info.family == os::kAddressFamilyUnix)
	{
		const int32_t path_len = (int32_t) strlen (info.data.path);
		
		socket_address->data = vm::Array::New (il2cpp_defaults.byte_class, 3 + path_len);
		
		il2cpp_array_set (socket_address->data, uint8_t, 0, (family >> 0) & 0xFF);
		il2cpp_array_set (socket_address->data, uint8_t, 1, (family >> 8) & 0xFF);
		
		for (int32_t i = 0; i <= path_len; i++)
			il2cpp_array_set (socket_address->data, uint8_t, i + 2, info.data.path[i]);
		
		il2cpp_array_set (socket_address->data, uint8_t, 2 + path_len, 0);
	}
	else if (info.family == os::kAddressFamilyInterNetworkV6)
	{
		// Allocate a 30 byte array, 2 bytes for the family, and 28 bytes for the IPv6 address and port
		socket_address->data = vm::Array::New (il2cpp_defaults.byte_class, 30);

		il2cpp_array_set (socket_address->data, uint8_t, 0, (family >> 0) & 0xFF);
		il2cpp_array_set (socket_address->data, uint8_t, 1, (family >> 8) & 0xFF);
		
		for (int i = 0; i < 28; ++i)
			il2cpp_array_set (socket_address->data, uint8_t, i + 2, info.data.raw[i]);
	}
	else
	{
		// Not supported
		return NULL;
	}
	
	return socket_address;
}

static bool check_thread_status ()
{
	static os::FastMutex _mutex;
	
	os::FastAutoLock lock(&_mutex);
	
	Il2CppThread *current_thread = vm::Thread::Current ();
	const vm::ThreadState state = vm::Thread::GetState (current_thread);
	
	if (state & vm::kThreadStateAbortRequested)
		return false;
	
	if (state & vm::kThreadStateSuspendRequested)
	{
		assert (0 && "kThreadStateSuspendRequested not supported yet!");
		return true;
	}
	
	if (state & vm::kThreadStateStopRequested)
		return false;
	
	if (current_thread->interruption_requested)
	{
		assert (0 && "thread->interruption_requested not supported yet!");
		return false;
	}
	
	return true;
}

/// Acquire os::SocketHandle in IntPtr "socket" for the current scope.
#define AUTO_ACQUIRE_SOCKET \
	os::SocketHandleWrapper socketHandle (os::PointerToSocketHandle (socket.m_value))

#define RETURN_IF_SOCKET_IS_INVALID(...) \
	if (!socketHandle.IsValid ()) \
	{ \
		*error = os::kErrorCodeInvalidHandle; \
		return __VA_ARGS__; \
	}

Il2CppIntPtr Socket::Accept (Il2CppIntPtr socket, int32_t* error, bool blocking)
{
	*error = 0;
	
	os::Socket *new_sock = NULL;
	
	AUTO_ACQUIRE_SOCKET;

	if (socketHandle.IsValid ())
	{
		const os::WaitStatus status = socketHandle->Accept (&new_sock);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else
	{
		*error = os::kErrorCodeInvalidHandle;
	}
	
	Il2CppIntPtr ret;
	if (new_sock)
		ret.m_value = reinterpret_cast<void*>(static_cast<uintptr_t>(os::CreateSocketHandle (new_sock)));
	else
		ret.m_value = NULL;
	
	return ret;
}

int32_t Socket::Available (Il2CppIntPtr socket, int32_t *error)
{
	*error = 0;
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID (0);
	
	int32_t available = 0;
	
	const os::WaitStatus status = socketHandle->Available (&available);
	
	if (status == os::kWaitStatusFailure)
	{
		*error = socketHandle->GetLastError ();
		return 0;
	}
	
	return available;
}

void UnpackIPv6AddressFromBuffer(const uint8_t* buffer, int32_t length, uint16_t* port, uint8_t* address, uint32_t* scope)
{
	if (length < 28)
	{
		vm::Exception::Raise (vm::Exception::GetSystemException ());
		return;
	}

	*port = ((buffer[2] << 8) | buffer[3]);
	
	for (int i = 0; i < ipv6AddressSize; i++)
		address[i] = buffer[i + 8];

	*scope = (uint32_t)((buffer[27] << 24) +
	                    (buffer[26] << 16) +
	                    (buffer[25] << 8) +
	                    (buffer[24]));
}

void Socket::Bind (Il2CppIntPtr socket, Il2CppSocketAddress* socket_address, int32_t* error)
{
	*error = 0;
	
	const int32_t length = socket_address->data->max_length;
	const uint8_t *buffer = (uint8_t*)il2cpp::vm::Array::GetFirstElementAddress (socket_address->data);
	
	if (length < 2)
	{
		vm::Exception::Raise (vm::Exception::GetSystemException ());
		return;
	}
	
	const os::AddressFamily family = convert_address_family ((AddressFamily)(buffer[0] | (buffer[1] << 8)));
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID ();
	
	if (family == os::kAddressFamilyInterNetwork)
	{
		if (length < 8)
		{
			vm::Exception::Raise (vm::Exception::GetSystemException ());
			return;
		}
		
		const uint16_t port = ((buffer[2] << 8) | buffer[3]);
		const uint32_t address = ((buffer[4] << 24) | (buffer[5] << 16) | (buffer[6] << 8) | buffer[7]);
		
		const os::WaitStatus status = socketHandle->Bind (address, port);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else if (family == os::kAddressFamilyUnix)
	{
		if (length - 2 >= END_POINT_MAX_PATH_LEN)
		{
			vm::Exception::Raise (vm::Exception::GetSystemException ());
			return;
		}
		
		char path[END_POINT_MAX_PATH_LEN] = {0};
		
		for (int32_t i = 0; i < (length - 2); ++i)
		{
			path[i] = (char)buffer[i + 2];
		}
		
		const os::WaitStatus status = socketHandle->Bind (path);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else if (family == os::kAddressFamilyInterNetworkV6)
	{
		uint16_t port;
		uint8_t address[ipv6AddressSize] = {0};
		uint32_t scope;
		UnpackIPv6AddressFromBuffer(buffer, length, &port, address, &scope);
		
		const os::WaitStatus status = socketHandle->Bind (address, scope, port);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else
	{
		*error = os::kWSAeafnosupport;
		return;
	}
}

void Socket::Blocking (Il2CppIntPtr socket, bool block, int32_t* error)
{
	*error = 0;
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID ();
	
	const os::WaitStatus status = socketHandle->SetBlocking (block);
	
	if (status == os::kWaitStatusFailure)
		*error = socketHandle->GetLastError ();
}

void Socket::Close (Il2CppIntPtr socket, int32_t* error)
{
	*error = 0;
	
	// Socket::Close get invoked when running the finalizers; in case Socket_internal
	// didn't succeed, the socket.m_value has a NULL value and thus we don't need to do
	// anything here.
	if (os::PointerToSocketHandle (socket.m_value) == os::kInvalidSocketHandle)
		return;

	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID ();

	socket.m_value = (void*) os::kInvalidSocketHandle;

	socketHandle->Close ();

	// There is an implicit acquisition happening when we create the socket which we undo
	// now that we have closed the socket.
	os::ReleaseSocketHandle (socketHandle.GetHandle ());
}

void Socket::Connect (Il2CppIntPtr socket, Il2CppSocketAddress* socket_address, int32_t* error)
{
	*error = 0;
	
	const int32_t length = socket_address->data->max_length;
	const uint8_t *buffer = (uint8_t*)il2cpp::vm::Array::GetFirstElementAddress (socket_address->data);
	
	if (length < 2)
	{
		vm::Exception::Raise (vm::Exception::GetSystemException ());
		return;
	}
	
	const os::AddressFamily family = convert_address_family ((AddressFamily)(buffer[0] | (buffer[1] << 8)));
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID ();
	
	if (family == os::kAddressFamilyInterNetwork)
	{
		if (length < 8)
		{
			vm::Exception::Raise (vm::Exception::GetSystemException ());
			return;
		}
		
		const uint16_t port = ((buffer[2] << 8) | buffer[3]);
		const uint32_t address = ((buffer[4] << 24) | (buffer[5] << 16) | (buffer[6] << 8) | buffer[7]);
		
		const os::WaitStatus status = socketHandle->Connect (address, port);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else if (family == os::kAddressFamilyUnix)
	{
		if (length - 2 >= END_POINT_MAX_PATH_LEN)
		{
			vm::Exception::Raise (vm::Exception::GetSystemException ());
			return;
		}
		
		char path[END_POINT_MAX_PATH_LEN] = {0};
		
		for (int32_t i = 0; i < (length - 2); ++i)
		{
			path[i] = (char)buffer[i + 2];
		}
		
		const os::WaitStatus status = socketHandle->Connect (path);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else if (family == os::kAddressFamilyInterNetworkV6)
	{
		uint16_t port;
		uint8_t address[ipv6AddressSize] = {0};
		uint32_t scope;
		UnpackIPv6AddressFromBuffer(buffer, length, &port, address, &scope);
		
		const os::WaitStatus status = socketHandle->Connect (address, scope, port);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else
	{
		*error = os::kWSAeafnosupport;
		return;
	}
}

void Socket::Disconnect (Il2CppIntPtr socket, bool reuse, int32_t *error)
{
	*error = 0;
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID ();
	
	const os::WaitStatus status = socketHandle->Disconnect (reuse);
	
	if (status == os::kWaitStatusFailure)
		*error = socketHandle->GetLastError ();
}

void Socket::GetSocketOptionArray (Il2CppIntPtr socket, SocketOptionLevel level, SocketOptionName name, Il2CppArray **byte_val, int32_t *error)
{
	*error = 0;
	
	// Note: for now the options map one to one.
	const os::SocketOptionName system_name = (os::SocketOptionName) (name);
	const os::SocketOptionLevel system_level = (os::SocketOptionLevel) (level);
	
	int32_t length = (*byte_val)->max_length;
	uint8_t *buffer = (uint8_t*)il2cpp::vm::Array::GetFirstElementAddress ((*byte_val));
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID ();
	
	const os::WaitStatus status = socketHandle->GetSocketOption (system_level, system_name, buffer, &length);
	
	if (status == os::kWaitStatusFailure)
		*error = socketHandle->GetLastError ();
}

void Socket::GetSocketOptionObj (Il2CppIntPtr socket, SocketOptionLevel level, SocketOptionName name, Il2CppObject **obj_val, int32_t *error)
{
	*error = 0;
	
	// Note: for now the options map one to one.
	const os::SocketOptionName system_name = (os::SocketOptionName) (name);
	const os::SocketOptionLevel system_level = (os::SocketOptionLevel) (level);
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID ();
	
	int32_t first = 0;
	int32_t second = 0;
	
	const os::WaitStatus status = socketHandle->GetSocketOptionFull (system_level, system_name, &first, &second);
	
	if (status == os::kWaitStatusFailure)
	{
		*error = socketHandle->GetLastError ();
		return;
	}
	
	switch (name)
	{
		case kSocketOptionNameLinger:
			{
				static Il2CppClass *System_Net_Sockets_LingerOption = NULL;
				
				if (!System_Net_Sockets_LingerOption)
				{
					System_Net_Sockets_LingerOption = vm::Class::FromName (
						vm::Assembly::GetImage (
							vm::Assembly::Load ("System.dll")),
						"System.Net.Sockets", "LingerOption");
				}
				
				*obj_val = vm::Object::New (System_Net_Sockets_LingerOption);
				
				const FieldInfo *enabled_field_info = vm::Class::GetFieldFromName (System_Net_Sockets_LingerOption, "enabled");
				const FieldInfo *seconds_field_info = vm::Class::GetFieldFromName (System_Net_Sockets_LingerOption, "seconds");
				
				*((bool*)((char*)(*obj_val) + enabled_field_info->offset)) = (first ? 1 : 0);
				*((int32_t*)((char*)(*obj_val) + seconds_field_info->offset)) = second;
			}
			
			break;
			
		case kSocketOptionNameDontLinger:
		case kSocketOptionNameSendTimeout:
		case kSocketOptionNameReceiveTimeout:
		default:
			*obj_val = vm::Object::Box(il2cpp_defaults.int32_class, &first);
			break;
	}
}

void Socket::Listen (Il2CppIntPtr socket, int32_t backlog, int32_t *error)
{
	*error = 0;
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID ();
	
	const os::WaitStatus status = socketHandle->Listen (backlog);
	
	if (status == os::kWaitStatusFailure)
		*error = socketHandle->GetLastError ();
}

static os::PollFlags select_mode_to_poll_flags (SelectMode mode)
{
	switch (mode)
	{
		case kSelectModeSelectRead:
			return os::kPollFlagsIn;
			
		case kSelectModeSelectWrite:
			return os::kPollFlagsOut;
			
		case kSelectModeSelectError:
			return os::kPollFlagsErr;
	}
	
	return os::kPollFlagsNone;
}

bool Socket::Poll (Il2CppIntPtr socket, SelectMode mode, int32_t timeout, int32_t *error)
{
	*error = 0;
	
	os::PollRequest request;

	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID (false);

	request.fd = socketHandle.GetSocket()->GetDescriptor();
	request.events = select_mode_to_poll_flags (mode);
	request.revents = os::kPollFlagsNone;
	
	if (request.events == os::kPollFlagsNone)
	{
		*error = os::kWSAefault;
		return false;
	}
	
	std::vector<os::PollRequest> requests;
	
	requests.push_back (request);
	
	int32_t results = 0;
	const os::WaitStatus result = os::Socket::Poll (requests, timeout, &results, error);
	
	if (result == os::kWaitStatusFailure || results == 0)
		return false;
	
	return (requests[0].revents != os::kPollFlagsNone);
}

int32_t Socket::ReceiveArray (Il2CppIntPtr socket, Il2CppArray *buffers, SocketFlags flags, int32_t *error)
{
	*error = 0;
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID (0);
	
	const int32_t count = buffers->max_length;
	const os::SocketFlags c_flags = convert_socket_flags (flags);
	
	os::WSABuf *wsabufs = il2cpp_array_addr (buffers, os::WSABuf, 0);
	
	int32_t len = 0;

	const os::WaitStatus status = socketHandle->ReceiveArray (wsabufs, count, &len, c_flags);
	
	if (status == os::kWaitStatusFailure)
	{
		*error = socketHandle->GetLastError ();
		return 0;
	}
	
	return len;
}

int32_t Socket::Receive (Il2CppIntPtr socket, Il2CppArray *buffer, int32_t offset, int32_t count, SocketFlags flags, int32_t *error)
{
	*error = 0;
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID (0);

	const int32_t a_len = buffer->max_length;
	if (offset > a_len - count)
		return 0;
	
	const os::SocketFlags c_flags = convert_socket_flags (flags);
	const uint8_t *data = il2cpp_array_addr (buffer, uint8_t, offset);
	
	int32_t len = 0;

	const os::WaitStatus status = socketHandle->Receive (data, count, c_flags, &len);
	
	if (status == os::kWaitStatusFailure)
		*error = socketHandle->GetLastError ();
	
	return len;
}

int32_t Socket::RecvFrom (Il2CppIntPtr socket, Il2CppArray *buffer, int32_t offset, int32_t count, SocketFlags flags, Il2CppSocketAddress **socket_address, int32_t *error)
{
	*error = 0;
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID (0);

	const int32_t a_len = buffer->max_length;
	if (offset > a_len - count)
		return 0;
	
	const os::SocketFlags c_flags = convert_socket_flags (flags);
	const uint8_t *data = il2cpp_array_addr (buffer, uint8_t, offset);
	
	int32_t len = 0;
	
	const int32_t length = (*socket_address)->data->max_length;
	const uint8_t *socket_buffer = (uint8_t*)il2cpp::vm::Array::GetFirstElementAddress ((*socket_address)->data);
	
	if (length < 2)
	{
		vm::Exception::Raise (vm::Exception::GetSystemException ());
		return 0;
	}
	
	const os::AddressFamily family = convert_address_family ((AddressFamily)(socket_buffer[0] | (socket_buffer[1] << 8)));
	
	os::EndPointInfo info;
	
	info.family = os::kAddressFamilyError;
	
	if (family == os::kAddressFamilyInterNetwork)
	{
		if (length < 8)
		{
			vm::Exception::Raise (vm::Exception::GetSystemException ());
			return 0;
		}
		
		const uint16_t port = ((socket_buffer[2] << 8) | socket_buffer[3]);
		const uint32_t address = ((socket_buffer[4] << 24) | (socket_buffer[5] << 16) | (socket_buffer[6] << 8) | socket_buffer[7]);
		
		const os::WaitStatus status = socketHandle->RecvFrom (address, port, data, count, c_flags, &len, info);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else if (family == os::kAddressFamilyUnix)
	{
		if (length - 2 >= END_POINT_MAX_PATH_LEN)
		{
			vm::Exception::Raise (vm::Exception::GetSystemException ());
			return 0;
		}
		
		char path[END_POINT_MAX_PATH_LEN] = {0};
		
		for (int32_t i = 0; i < (length - 2); ++i)
		{
			path[i] = (char)socket_buffer[i + 2];
		}
		
		const os::WaitStatus status = socketHandle->RecvFrom (path, data, count, c_flags, &len, info);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else if (family == os::kAddressFamilyInterNetworkV6)
	{
		uint16_t port;
		uint8_t address[ipv6AddressSize] = {0};
		uint32_t scope;
		UnpackIPv6AddressFromBuffer(socket_buffer, length, &port, address, &scope);
		
		const os::WaitStatus status = socketHandle->RecvFrom (address, scope, port, data, count, c_flags, &len, info);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else
	{
		*error = os::kWSAeafnosupport;
		return 0;
	}
	
	*socket_address = (info.family == os::kAddressFamilyError) ? NULL : end_point_info_to_socket_address(info);
	
	return len;
}

Il2CppSocketAddress* Socket::LocalEndPoint (Il2CppIntPtr socket, int32_t* error)
{
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID (NULL);
	
	os::EndPointInfo info;
	
	memset (&info, 0x00, sizeof (os::EndPointInfo));
	
	const os::WaitStatus status = socketHandle->GetLocalEndPointInfo (info);
	
	if (status == os::kWaitStatusFailure)
	{
		*error = socketHandle->GetLastError ();
		return NULL;
	}
	
	Il2CppSocketAddress *socket_address = end_point_info_to_socket_address (info);
	
	if (socket_address == NULL)
		*error = os::kWSAeafnosupport;
	
	return socket_address;
}

Il2CppSocketAddress* Socket::RemoteEndPoint (Il2CppIntPtr socket, int32_t* error)
{
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID (NULL);
	
	os::EndPointInfo info;
	
	memset (&info, 0x00, sizeof (os::EndPointInfo));
	
	const os::WaitStatus status = socketHandle->GetRemoteEndPointInfo (info);
	
	if (status == os::kWaitStatusFailure)
	{
		*error = socketHandle->GetLastError ();
		return NULL;
	}
	
	Il2CppSocketAddress *socket_address = end_point_info_to_socket_address (info);
	
	if (socket_address == NULL)
		*error = os::kWSAeafnosupport;
	
	return socket_address;
}

void Socket::Select (Il2CppArray **sockets, int32_t timeout, int32_t *error)
{
	*error = 0;
	
	// Layout: READ, null, WRITE, null, ERROR, null
	const uint32_t input_sockets_count = (*sockets)->max_length;
	
	std::vector<os::PollRequest> requests;
	std::vector<os::SocketHandleWrapper> socketHandles;
	
	requests.reserve(input_sockets_count - 3);
	
	int32_t mode = 0;
	
	for (uint32_t i = 0; i < input_sockets_count; ++i)
	{
		Il2CppObject *obj = il2cpp_array_get (*sockets, Il2CppObject*, i);
		
		if (obj == NULL)
		{
			if (++mode > 3)
			{
				// Something very bad happened here (ie: the input array was wrong)
				// so we gracefully terminate the method.
				*error = os::kWSAefault;
				return;
			}
			
			continue;
		}
		
		const FieldInfo *field_info = vm::Class::GetFieldFromName (obj->klass, "socket");
		Il2CppIntPtr& intPtr = *((Il2CppIntPtr*) ((char*) obj + field_info->offset));

		// Acquire socket.
		socketHandles.push_back (os::SocketHandleWrapper ());
		os::SocketHandleWrapper& socketHandle = socketHandles.back ();
		socketHandle.Acquire (os::PointerToSocketHandle (intPtr.m_value));

		os::PollRequest request;
		// May 'invalid socket' (-1); we want the error from Poll() in that case.
		request.fd = socketHandle.GetSocket() == NULL ? -1 : socketHandle.GetSocket()->GetDescriptor();
		request.events = (mode == 0 ? os::kPollFlagsIn : (mode == 1 ? os::kPollFlagsOut : os::kPollFlagsErr));
		request.revents = os::kPollFlagsNone;
		
		requests.push_back (request);
	}
	
	if (requests.size () == 0)
		return;
	
	int32_t results = 0;
	const os::WaitStatus result = os::Socket::Poll (requests, timeout, &results, error);
	
	if (result == os::kWaitStatusFailure)
	{
		*sockets = NULL;
		return;
	}
	
	Il2CppArray *new_sockets = vm::Array::New (((Il2CppObject*)(*sockets))->klass->element_class, results + 3);
	
	if (results > 0)
	{
		mode = 0;

		uint32_t request_index = 0;

		// This odd loop is due to the layout of the sockets input array:
		// Layout: READ, null, WRITE, null, ERROR, null
		// We need to iterate each request and iterate the sockets array, skipping
		// the null entries. We try to avoid an infinite loop here as well.
		uint32_t add_index = 0;
		while (request_index < requests.size())
		{
			const uint32_t input_sockets_index = (request_index + mode);
			if (input_sockets_index > input_sockets_count - 1)
				break; // We have exhausted the input sockets array, so exit.

			Il2CppObject *obj = il2cpp_array_get(*sockets, Il2CppObject*, input_sockets_index);

			if (obj == NULL)
			{
				++mode;
				continue; // Here is a null entry, skip it without updated the next request index.
			}

			os::PollRequest &request = requests[request_index];

			if (request.revents != os::kPollFlagsNone)
			{
				switch (mode)
				{
				case 0:
					if (request.revents & (os::kPollFlagsIn | os::kPollFlagsErr))
					{
						il2cpp_array_setref (new_sockets, (add_index + mode), obj);
						add_index++;
					}
					break;

				case 1:
					if (request.revents & (os::kPollFlagsOut | os::kPollFlagsErr))
					{
						il2cpp_array_setref (new_sockets, (add_index + mode), obj);
						add_index++;
					}
					break;

				default:
					if (request.revents & os::kPollFlagsErr)
					{
						il2cpp_array_setref (new_sockets, (add_index + mode), obj);
						add_index++;
					}
					break;
				}
			}

			++request_index;
		}
	}
	
	*sockets = new_sockets;
}

bool Socket::SendFile (Il2CppIntPtr socket, Il2CppString *filename, Il2CppArray *pre_buffer, Il2CppArray *post_buffer, TransmitFileOptions flags)
{
	if (filename == NULL)
		return false;
	
	os::TransmitFileBuffers t_buffers = {0};
	
	if (pre_buffer != NULL)
	{
		t_buffers.head = il2cpp_array_addr (pre_buffer, uint8_t, 0);
		t_buffers.head_length = pre_buffer->max_length;
	}
	
	if (post_buffer != NULL)
	{
		t_buffers.tail = il2cpp_array_addr (post_buffer, uint8_t, 0);
		t_buffers.tail_length = post_buffer->max_length;
	}
	
	AUTO_ACQUIRE_SOCKET;
	if (!socketHandle.IsValid ())
		return false;
	
	const Il2CppChar* ustr = vm::String::GetChars (filename);
	const std::string str = utils::StringUtils::Utf16ToUtf8 (ustr);
	
	// Note: for now they map 1-1
	const os::TransmitFileOptions o_flags = (os::TransmitFileOptions) flags;
	const os::WaitStatus status = socketHandle->SendFile (str.c_str (), &t_buffers, o_flags);
	
	if (status == os::kWaitStatusFailure)
	{
		// TODO: mono stores socketHandle->GetLastError into a threadlocal global variable
		// that can be retrieved later by other icalls.
		return false;
	}
	
	if ((flags & kTransmitFileOptionsDisconnect) == kTransmitFileOptionsDisconnect)
		socketHandle->Disconnect (true);
	
	return true;
}

int32_t Socket::SendTo (Il2CppIntPtr socket, Il2CppArray *buffer, int32_t offset, int32_t count, SocketFlags flags, Il2CppSocketAddress *socket_address, int32_t *error)
{
	*error = 0;
	
	const int32_t a_len = buffer->max_length;
	if (offset > a_len - count)
		return 0;
	
	const os::SocketFlags c_flags = convert_socket_flags (flags);
	const uint8_t *data = il2cpp_array_addr (buffer, uint8_t, offset);
	
	int32_t len = 0;
	
	const int32_t length = socket_address->data->max_length;
	const uint8_t *socket_buffer = (uint8_t*)il2cpp::vm::Array::GetFirstElementAddress (socket_address->data);
	
	if (length < 2)
	{
		vm::Exception::Raise (vm::Exception::GetSystemException ());
		return 0;
	}
	
	const os::AddressFamily family = convert_address_family ((AddressFamily)(socket_buffer[0] | (socket_buffer[1] << 8)));
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID (0);
	
	if (family == os::kAddressFamilyInterNetwork)
	{
		if (length < 8)
		{
			vm::Exception::Raise (vm::Exception::GetSystemException ());
			return 0;
		}
		
		const uint16_t port = ((socket_buffer[2] << 8) | socket_buffer[3]);
		const uint32_t address = ((socket_buffer[4] << 24) | (socket_buffer[5] << 16) | (socket_buffer[6] << 8) | socket_buffer[7]);
		
		const os::WaitStatus status = socketHandle->SendTo (address, port, data, count, c_flags, &len);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else if (family == os::kAddressFamilyUnix)
	{
		if (length - 2 >= END_POINT_MAX_PATH_LEN)
		{
			vm::Exception::Raise (vm::Exception::GetSystemException ());
			return 0;
		}
		
		char path[END_POINT_MAX_PATH_LEN] = {0};
		
		for (int32_t i = 0; i < (length - 2); ++i)
		{
			path[i] = (char)socket_buffer[i + 2];
		}
		
		const os::WaitStatus status = socketHandle->SendTo (path, data, count, c_flags, &len);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else if (family == os::kAddressFamilyInterNetworkV6)
	{
		uint16_t port;
		uint8_t address[ipv6AddressSize] = {0};
		uint32_t scope;
		UnpackIPv6AddressFromBuffer(socket_buffer, length, &port, address, &scope);
		
		const os::WaitStatus status = socketHandle->SendTo (address, scope, port, data, count, c_flags, &len);
		
		if (status == os::kWaitStatusFailure)
			*error = socketHandle->GetLastError ();
	}
	else
	{
		*error = os::kWSAeafnosupport;
		return 0;
	}
	
	return len;
}

int32_t Socket::SendArray (Il2CppIntPtr socket, Il2CppArray *buffers, SocketFlags flags, int32_t *error)
{
	*error = 0;
	
	const int32_t count = buffers->max_length;
	const os::SocketFlags c_flags = convert_socket_flags (flags);
	
	os::WSABuf *wsabufs = il2cpp_array_addr (buffers, os::WSABuf, 0);
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID (0);
	
	int32_t sent = 0;
	
	const os::WaitStatus status = socketHandle->SendArray (wsabufs, count, &sent, c_flags);
	
	if (status == os::kWaitStatusFailure)
	{
		*error = socketHandle->GetLastError ();
		return 0;
	}
	
	return sent;
}

int32_t Socket::Send (Il2CppIntPtr socket, Il2CppArray *buffer, int32_t offset, int32_t count, SocketFlags flags, int32_t *error)
{
	*error = 0;
	
	const int32_t a_len = buffer->max_length;
	if (offset > a_len - count)
		return 0;
	
	const os::SocketFlags c_flags = convert_socket_flags (flags);
	const uint8_t *data = il2cpp_array_addr (buffer, uint8_t, offset);
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID (0);
	
	int32_t len = 0;

	const os::WaitStatus status = socketHandle->Send (data, count, c_flags, &len);
	
	if (status == os::kWaitStatusFailure)
		*error = socketHandle->GetLastError ();
	
	return len;
}

void Socket::SetSocketOption (Il2CppIntPtr socket, SocketOptionLevel level, SocketOptionName name, Il2CppObject *obj_val, Il2CppArray *byte_val, int32_t int_val, int32_t *error)
{
	*error = 0;
	
	// Note: for now the options map one to one.
	const os::SocketOptionName system_name = (os::SocketOptionName) (name);
	const os::SocketOptionLevel system_level = (os::SocketOptionLevel) (level);
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID ();
	
	os::WaitStatus status = os::kWaitStatusFailure;
	
	if (byte_val != NULL)
	{
		const int32_t length = byte_val->max_length;
		const uint8_t *buffer = (uint8_t*)il2cpp::vm::Array::GetFirstElementAddress (byte_val);
		
		status = socketHandle->SetSocketOptionArray (system_level, system_name, buffer, length);
	}
	else if (obj_val != NULL)
	{
		switch (name)
		{
			case kSocketOptionNameLinger:
				{
					const FieldInfo *enabled_field_info = vm::Class::GetFieldFromName (obj_val->klass, "enabled");
					const FieldInfo *seconds_field_info = vm::Class::GetFieldFromName (obj_val->klass, "seconds");
					
					const bool enabled = *((bool*)((char*)obj_val + enabled_field_info->offset));
					const int32_t seconds = *((int32_t*)((char*)obj_val + seconds_field_info->offset));
					
					status = socketHandle->SetSocketOptionLinger (system_level, system_name, enabled, seconds);
				}
				
				break;
				
			case kSocketOptionNameAddMembership:
			case kSocketOptionNameDropMembership:
				{
					FieldInfo *group_field_info = vm::Class::GetFieldFromName (obj_val->klass, "group");
					FieldInfo *local_field_info = vm::Class::GetFieldFromName (obj_val->klass, "local");
					
					Il2CppObject* group_obj = vm::Field::GetValueObject(group_field_info, obj_val);
					Il2CppObject* local_obj = vm::Field::GetValueObject(local_field_info, obj_val);

					const FieldInfo *group_address_field_info = vm::Class::GetFieldFromName (group_obj->klass, "m_Address");
					const FieldInfo *local_address_field_info = vm::Class::GetFieldFromName (local_obj->klass, "m_Address");
					
					const uint32_t group_address = *((uint32_t*)(uint64_t*)((char*)group_obj + group_address_field_info->offset));
					const uint32_t local_address = *((uint32_t*)(uint64_t*)((char*)local_obj + local_address_field_info->offset));
					
					status = socketHandle->SetSocketOptionMembership (system_level, system_name, group_address, local_address);
				}
				
				break;
			
			default:
				*error = os::kWSAeinval;
				return; // early out
		}
	}
	else
		status = socketHandle->SetSocketOption (system_level, system_name, int_val);
	
	if (status == os::kWaitStatusFailure)
		*error = socketHandle->GetLastError ();
}

void Socket::Shutdown (Il2CppIntPtr socket, SocketShutdown how, int32_t* error)
{
	*error = 0;
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID ();
	
	const os::WaitStatus status = socketHandle->Shutdown (how);
	
	if (status == os::kWaitStatusFailure)
		*error = socketHandle->GetLastError ();
}

Il2CppIntPtr Socket::Socket_internal (Il2CppObject *self, AddressFamily family, SocketType type, ProtocolType protocol, int32_t* error)
{
	Il2CppIntPtr socket = { NULL };
	
	*error = 0;
	
	os::AddressFamily n_family = convert_address_family (family);
	os::SocketType n_type = convert_socket_type (type);
	os::ProtocolType n_protocol = convert_socket_protocol (protocol);
	
	if (n_family == os::kAddressFamilyError)
	{
		*error = os::kWSAeafnosupport;
		return socket;
	}
	
	if (n_type == os::kSocketTypeError)
	{
		*error = os::kWSAesocktnosupport;
		return socket;
	}
	
	if (n_protocol == os::kProtocolTypeUnknown)
	{
		*error = os::kWSAeprotonosupport;
		return socket;
	}
	
	os::Socket *sock = new os::Socket (check_thread_status);
	
	const os::WaitStatus status = sock->Create (n_family, n_type, n_protocol);
	
	if (status == os::kWaitStatusFailure)
	{
		*error = sock->GetLastError ();

		// Okay to delete socket directly. We haven't created a handle yet.
		delete sock;

		return socket;
	}
	
	os::SocketHandle socketHandle = os::CreateSocketHandle (sock);
	socket.m_value = reinterpret_cast<void*>(static_cast<uintptr_t>(socketHandle));
	
	return socket;
}

int32_t Socket::WSAIoctl (Il2CppIntPtr socket, int32_t code, Il2CppArray *input, Il2CppArray *output, int32_t *error)
{
	*error = 0;
	
	AUTO_ACQUIRE_SOCKET;
	RETURN_IF_SOCKET_IS_INVALID (-1);
	
	if (code == 0x5421 /* FIONBIO */)
	{
		// Invalid command. Must use Socket.Blocking
		return -1;
	}
	
	const int32_t in_length = (input ? input->max_length : 0);
	const uint8_t *in_buffer = (input ? (uint8_t*)il2cpp::vm::Array::GetFirstElementAddress (input) : NULL);
	
	const int32_t out_length = (output ? output->max_length : 0);
	uint8_t *out_buffer = (output ? (uint8_t*)il2cpp::vm::Array::GetFirstElementAddress (output) : NULL);
	
	int32_t output_bytes = 0;
	
	const os::WaitStatus status = socketHandle->Ioctl (code, in_buffer, in_length, out_buffer, out_length, &output_bytes);
	
	if (status == os::kWaitStatusFailure)
	{
		*error = socketHandle->GetLastError ();
		return -1;
	}
	
	return output_bytes;
}

} /* namespace Sockets */
} /* namespace Net */
} /* namespace System */
} /* namespace System */
} /* namespace icalls */
} /* namespace il2cpp */
